package com.ruoyi.common.utils;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.FileImageOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

@Slf4j
public class BitOperatorUtil {

    public static byte integerTo1Byte(int value) {
        return (byte) (value & 0xFF);
    }

    public static byte[] integerTo1Bytes(int value) {
        byte[] result = new byte[1];
        result[0] = (byte) (value & 0xFF);
        return result;
    }

    public static byte[] integerTo2Bytes(int value) {
        byte[] result = new byte[2];
        result[0] = (byte) (value >>> 8 & 0xFF);
        result[1] = (byte) (value & 0xFF);
        return result;
    }

    public byte[] integerTo3Bytes(int value) {
        byte[] result = new byte[3];
        result[0] = (byte) (value >>> 16 & 0xFF);
        result[1] = (byte) (value >>> 8 & 0xFF);
        result[2] = (byte) (value & 0xFF);
        return result;
    }

    public byte[] integerTo4Bytes(int value) {
        byte[] result = new byte[4];
        result[0] = (byte) (value >>> 24 & 0xFF);
        result[1] = (byte) (value >>> 16 & 0xFF);
        result[2] = (byte) (value >>> 8 & 0xFF);
        result[3] = (byte) (value & 0xFF);
        return result;
    }

    public static int byteToInteger(byte[] value) {
        int result;
        if (value.length == 1) {
            result = oneByteToInteger(value[0]);
        } else if (value.length == 2) {
            result = twoBytesToInteger(value);
        } else if (value.length == 3) {
            result = threeBytesToInteger(value);
        } else if (value.length == 4) {
            result = fourBytesToInteger(value);
        } else {
            result = fourBytesToInteger(value);
        }
        return result;
    }

    public static int bytes2Int(byte[] b, int start, int len) {
        int sum = 0;
        int end = start + len;
        for (int i = start; i < end; i++) {
            int n = b[i] & 0xFF;
            n <<= --len * 8;
            sum += n;
        }
        return sum;
    }

    public static int oneByteToInteger(byte value) {
        return value & 0xFF;
    }

    public static int twoBytesToInteger(byte[] value) {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        return (temp0 << 8) + temp1;
    }

    public static int threeBytesToInteger(byte[] value) {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        int temp2 = value[2] & 0xFF;
        return (temp0 << 16) + (temp1 << 8) + temp2;
    }

    public static int fourBytesToInteger(byte[] value) {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        int temp2 = value[2] & 0xFF;
        int temp3 = value[3] & 0xFF;
        return (temp0 << 24) + (temp1 << 16) + (temp2 << 8) + temp3;
    }

    public long fourBytesToLong(byte[] value) throws Exception {
        int temp0 = value[0] & 0xFF;
        int temp1 = value[1] & 0xFF;
        int temp2 = value[2] & 0xFF;
        int temp3 = value[3] & 0xFF;
        return (temp0 << 24L) + (temp1 << 16) + (temp2 << 8) + temp3;
    }

    public long bytes2Long(byte[] value) {
        long result = 0L;
        int len = value.length;
        for (int i = 0; i < len; i++) {
            int temp = (len - 1 - i) * 8;
            if (temp == 0) {
                result += (value[i] & 0xFF);
            } else {
                result += ((value[i] & 0xFF) << temp);
            }
        }
        return result;
    }

    public byte[] longToBytes(long value) {
        return longToBytes(value, 8);
    }

    public static byte[] longToBytes(long value, int len) {
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++) {
            int temp = (len - 1 - i) * 8;
            if (temp == 0) {
                result[i] = (byte) (int) (result[i] + (value & 0xFFL));
            } else {
                result[i] = (byte) (int) (result[i] + (value >>> temp & 0xFFL));
            }
        }
        return result;
    }

    public byte[] generateTransactionID() throws Exception {
        byte[] id = new byte[16];
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 0, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 2, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 4, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 6, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 8, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 10, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 12, 2);
        System.arraycopy(integerTo2Bytes((int) (Math.random() * 65536.0D)), 0, id, 14, 2);
        return id;
    }

    public int[] getIntIPValue(String ip) throws Exception {
        String[] sip = ip.split("[.]");
        int[] intIP = {Integer.parseInt(sip[0]), Integer.parseInt(sip[1]), Integer.parseInt(sip[2]), Integer.parseInt(sip[3])};
        return intIP;
    }

    public String getStringIPValue(byte[] address) throws Exception {
        int first = oneByteToInteger(address[0]);
        int second = oneByteToInteger(address[1]);
        int third = oneByteToInteger(address[2]);
        int fourth = oneByteToInteger(address[3]);
        return first + "." + second + "." + third + "." + fourth;
    }

    public byte[] concatAll(byte[] first, byte[]... rest) {
        int totalLength = first.length;
        for (byte[] array : rest) {
            if (array != null)
                totalLength += array.length;
        }
        byte[] result = Arrays.copyOf(first, totalLength);
        int offset = first.length;
        for (byte[] array : rest) {
            if (array != null) {
                System.arraycopy(array, 0, result, offset, array.length);
                offset += array.length;
            }
        }
        return result;
    }

    public static byte[] concatAll(List<byte[]> rest) {
        int totalLength = 0;
        for (byte[] array : rest) {
            if (array != null)
                totalLength += array.length;
        }
        byte[] result = new byte[totalLength];
        int offset = 0;
        for (byte[] array : rest) {
            if (array != null) {
                System.arraycopy(array, 0, result, offset, array.length);
                offset += array.length;
            }
        }
        return result;
    }

    public float byte2Float(byte[] bs) {
        int bits = 0;
        for (int i = 0; i < bs.length; i++)
            bits += (bs[i] & 0xFF) << 8 * i;
        return Float.intBitsToFloat(bits);
    }

    public float byteBE2Float(byte[] bytes) {
        int l = bytes[0];
        l &= 0xFF;
        l = (int) (l | bytes[1] << 8L);
        l &= 0xFFFF;
        l = (int) (l | bytes[2] << 16L);
        l &= 0xFFFFFF;
        l = (int) (l | bytes[3] << 24L);
        return Float.intBitsToFloat(l);
    }

    public static byte[] float2byte(float f) {
        int fbit = Float.floatToIntBits(f);
        byte[] b = new byte[4];
        for (int i = 0; i < 4; i++)
            b[i] = (byte) (fbit >> 24 - i * 8);
        int len = b.length;
        byte[] dest = new byte[len];
        System.arraycopy(b, 0, dest, 0, len);
        for (int j = 0; j < len / 2; j++) {
            byte temp = dest[j];
            dest[j] = dest[len - j - 1];
            dest[len - j - 1] = temp;
        }
        return dest;
    }

    public static long getCRC32Checksum(byte[] bytes) {
        Checksum crc32 = new CRC32();
        crc32.update(bytes, 0, bytes.length);
        return crc32.getValue();
    }

    public int getCheckSum4JT808(byte[] data, int start, int end) {
        if (start < 0 || end > data.length)
            throw new ArrayIndexOutOfBoundsException("getCheckSum4JT808 error : index out of bounds(start=" + start + ",end=" + end + ",bytes length=" + data.length + ")");
        int cs = 0;
        for (int j = start; j < end; j++)
            cs ^= parseIntFromBytes(data, j, 1);
        return cs;
    }

    public int getCheckSum4JT8081111(byte[] bs, int start, int end) {
        if (start < 0 || end > bs.length)
            throw new ArrayIndexOutOfBoundsException("getCheckSum4JT808 error : index out of bounds(start=" + start + ",end=" + end + ",bytes length=" + bs.length + ")");
        int cs = 0;
        for (int i = start; i < end; i++)
            cs ^= bs[i];
        return cs;
    }

    public int getBitRange(int number, int start, int end) {
        if (start < 0)
            throw new IndexOutOfBoundsException("min index is 0,but start = " + start);
        if (end >= 32)
            throw new IndexOutOfBoundsException("max index is 31,but end = " + end);
        return number << 32 - end + 1 >>> 32 - end - start + 1;
    }

    public int getBitAt(int number, int index) {
        if (index < 0)
            throw new IndexOutOfBoundsException("min index is 0,but " + index);
        if (index >= 32)
            throw new IndexOutOfBoundsException("max index is 31,but " + index);
        return (1 << index & number) >> index;
    }

    public int getBitAtS(int number, int index) {
        String s = Integer.toBinaryString(number);
        return Integer.parseInt(s.charAt(index) + "");
    }

    @Deprecated
    public int getBitRangeS(int number, int start, int end) {
        String s = Integer.toBinaryString(number);
        StringBuilder sb = new StringBuilder(s);
        while (sb.length() < 32)
            sb.insert(0, "0");
        String tmp = sb.reverse().substring(start, end + 1);
        sb = new StringBuilder(tmp);
        return Integer.parseInt(sb.reverse().toString(), 2);
    }

    public byte[][] split_bytes(byte[] bytes, int copies) {
        double split_length = Double.parseDouble(copies + "");
        int array_length = (int) Math.ceil(bytes.length / split_length);
        byte[][] result = new byte[array_length][];
        for (int i = 0; i < array_length; i++) {
            int from = (int) (i * split_length);
            int to = (int) (from + split_length);
            if (to > bytes.length)
                to = bytes.length;
            result[i] = Arrays.copyOfRange(bytes, from, to);
        }
        return result;
    }

    public Object[] splitAry(byte[] ary, int subSize) {
        int count = (ary.length % subSize == 0) ? (ary.length / subSize) : (ary.length / subSize + 1);
        List<List<Byte>> subAryList = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            int index = i * subSize;
            List<Byte> list = new ArrayList<>();
            int k = 0;
            while (k < subSize && index < ary.length) {
                list.add(Byte.valueOf(ary[index++]));
                k++;
            }
            subAryList.add(list);
        }
        Object[] subAry = new Object[subAryList.size()];
        for (int j = 0; j < subAryList.size(); j++) {
            List<Byte> subList = subAryList.get(j);
            byte[] subAryItem = new byte[subList.size()];
            for (int k = 0; k < subList.size(); k++)
                subAryItem[k] = ((Byte) subList.get(k)).byteValue();
            subAry[j] = subAryItem;
        }
        return subAry;
    }

    public ByteBuf byteToByteBuf(byte[] bs) {
        ByteBuf bf = Unpooled.buffer(bs.length);
        return bf.writeBytes(bs);
    }

    public byte[] image2byte(String path) {
        byte[] data = null;
        FileImageInputStream input = null;
        try {
            input = new FileImageInputStream(new File(path));
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            int numBytesRead = 0;
            while ((numBytesRead = input.read(buf)) != -1)
                output.write(buf, 0, numBytesRead);
            data = output.toByteArray();
            output.close();
            input.close();
        } catch (FileNotFoundException ex1) {
            ex1.printStackTrace();
        } catch (IOException ex1) {
            ex1.printStackTrace();
        }
        return data;
    }

    public void byte2image(byte[] data, String path) {
        if (data.length < 3 || path.equals(""))
            return;
        try {
            FileImageOutputStream imageOutput = new FileImageOutputStream(new File(path));
            imageOutput.write(data, 0, data.length);
            imageOutput.close();
            System.out.println("Make Picture success,Please find image in " + path);
        } catch (Exception ex) {
            System.out.println("Exception: " + ex);
            ex.printStackTrace();
        }
    }

    public String byte2string(byte[] data) {
        if (data == null || data.length <= 1)
            return "0x";
        if (data.length > 200000)
            return "0x";
        StringBuffer sb = new StringBuffer();
        int[] buf = new int[data.length];
        int k;
        for (k = 0; k < data.length; k++)
            buf[k] = (data[k] < 0) ? (data[k] + 256) : data[k];
        for (k = 0; k < buf.length; k++) {
            if (buf[k] < 16) {
                sb.append("0" + Integer.toHexString(buf[k]));
            } else {
                sb.append(Integer.toHexString(buf[k]));
            }
        }
        return "0x" + sb.toString().toUpperCase();
    }

    public static String bytes2HexString(byte[] b) {
        String r = "";
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1)
                hex = '0' + hex;
            r = r + hex.toUpperCase();
        }
        return r;
    }

    public static byte[] hexString2Bytes(String hex) {
        if (hex == null || hex.equals(""))
            return null;
        if (hex.length() % 2 != 0)
            return null;
        hex = hex.toUpperCase();
        int len = hex.length() / 2;
        byte[] b = new byte[len];
        char[] hc = hex.toCharArray();
        for (int i = 0; i < len; i++) {
            int p = 2 * i;
            b[i] = (byte) (charToByte(hc[p]) << 4 | charToByte(hc[p + 1]));
        }
        return b;
    }

    private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    public byte[] add0(String str, int num) {
        byte[] str_byte = str.getBytes();
        ByteBuf add_byteBuf = Unpooled.buffer(num - str_byte.length);
        for (int i = 0; i < num - str_byte.length; i++)
            add_byteBuf.writeByte(0);
        str_byte = concatAll((List) Arrays.asList(new byte[][]{add_byteBuf
                .array(), str_byte}));
        return str_byte;
    }

    public float parseFloatFromBytes(byte[] data, int startIndex, int length) {
        return parseFloatFromBytes(data, startIndex, length, 0.0F);
    }

    public float parseFloatFromBytes(byte[] data, int startIndex, int length, float defaultVal) {
        try {
            int len = (length > 4) ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return byte2Float(tmp);
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    public static byte[] byteMerger(byte[] bt1, byte[] bt2) {
        if (bt1 == null)
            return bt2;
        byte[] bt3 = new byte[bt1.length + bt2.length];
        System.arraycopy(bt1, 0, bt3, 0, bt1.length);
        System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length);
        return bt3;
    }

    public String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
        return parseStringFromBytes(data, startIndex, lenth, null);
    }

    public String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return new String(tmp, "GBK");
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    public String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
        return parseBcdStringFromBytes(data, startIndex, lenth, null);
    }

    private String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return bcd2String(tmp);
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    public static int parseIntFromBytes(byte[] data, int startIndex, int length) {
        return parseIntFromBytes(data, startIndex, length, 0);
    }

    public static int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
        try {
            int len = (length > 4) ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return byteToInteger(tmp);
        } catch (Exception e) {
            log.error("{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }

    public String bcd2String(byte[] bytes) {
        StringBuilder temp = new StringBuilder(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            temp.append((bytes[i] & 0xF0) >>> 4);
            temp.append(bytes[i] & 0xF);
        }
        return temp.toString();
    }

    public byte[] string2Bcd(String str) {
        if ((str.length() & 0x1) == 1)
            str = "0" + str;
        byte[] ret = new byte[str.length() / 2];
        byte[] bs = str.getBytes();
        for (int i = 0; i < ret.length; i++) {
            byte high = ascII2Bcd(bs[2 * i]);
            byte low = ascII2Bcd(bs[2 * i + 1]);
            ret[i] = (byte) (high << 4 | low);
        }
        return ret;
    }

    private byte ascII2Bcd(byte asc) {
        if (asc >= 48 && asc <= 57)
            return (byte) (asc - 48);
        if (asc >= 65 && asc <= 70)
            return (byte) (asc - 65 + 10);
        if (asc >= 97 && asc <= 102)
            return (byte) (asc - 97 + 10);
        return (byte) (asc - 48);
    }

    public static byte[] getBooleanArray(byte b) {
        byte[] array = new byte[8];
        for (int i = 7; i >= 0; i--) {
            array[i] = (byte) (b & 0x1);
            b = (byte) (b >> 1);
        }
        return array;
    }

    public static String byteToBit(byte b) {
        return "" + (byte) (b >> 7 & 0x1) + (byte) (b >> 6 & 0x1) + (byte) (b >> 5 & 0x1) + (byte) (b >> 4 & 0x1) + (byte) (b >> 3 & 0x1) + (byte) (b >> 2 & 0x1) + (byte) (b >> 1 & 0x1) + (byte) (b >> 0 & 0x1);
    }

    public static byte decodeBinaryString(String byteStr) {
        int re;
        if (null == byteStr)
            return 0;
        int len = byteStr.length();
        if (len != 4 && len != 8)
            return 0;
        if (len == 8) {
            if (byteStr.charAt(0) == '0') {
                re = Integer.parseInt(byteStr, 2);
            } else {
                re = Integer.parseInt(byteStr, 2) - 256;
            }
        } else {
            re = Integer.parseInt(byteStr, 2);
        }
        return (byte) re;
    }
}
